/*
*   author：封连祥，2024-11-19
*   根据力的排斥作用，重新定义更新y坐标，
*   stepCoff:步长系数，根据受到的力反推移动距离的系数。
*   k相对有边界的小球的斥力作用。默认为1
*   moveMin:最小移动距离。小于该距离就停止移动，到下一个点。
*   @param {*} maxHeight y方向的最大坐标值。注意不是最大屏幕上的高度。
*   当这小求所受力的平均数的绝对值小于endConditon.mean,方差小于endCondition.variance,就认为系统是均衡的。
*/
class GenerateCoor {
    constructor(obj){
        this.nodes = obj.nodes.map(item=>{
            return {
                ...item,
                y:obj.maxHeight/2,
                moveStep: 0,
                weight:item.import == '是' ? 1.2 : 1
            }
        });
        this.k = obj.k || 1;
        this.maxHeight = obj.maxHeight;
        this.yValue = obj.yValue;
        this.stepCoff = obj.stepCoff || 0.5;
        this.currentMoveIndex = 0;
        this.startMove = false;
        this.moveMin = 2;
        this.defaultStep = this.maxHeight / 20;
        this.minForce = 0.01;
        this.endCondition = {
            mean:0.01,
            variance:0.005
        }
    }
    /**
     * 
     * @param {*} p1 
     * @param {*} p2 
     * @returns the distance between p1 and p2
     */
    distance(p1,p2){
        return Math.sqrt((p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y))
    }
    
    /**
     * @param {*} p1 受力点
     * @param {*} p2 影响点
     * @returns 点p1 所承受的力，整数表示向上，负数表示向下
     */
    force(p1,p2){
        if(p2.distance >= this.maxHeight) return 0;
        if(p1.x == p2.x){
            return this.distanceToForce(p1.y - p2.y,p1)
        }else if(p1.y == p2.y){
            return 0;
        }else{
            let radia = Math.atan(Math.abs(p1.y-p2.y)/Math.abs(p1.x-p2.x))
            let direction = Math.abs(p1.y - p2.y)/(p1.y - p2.y);
            return  this.distanceToForce(p2.distance,p1) * Math.sin(radia)  * direction;
        }
    }

    /*
     * 
     * @param {*} distance 
     * @returns return force by distance
     */
    distanceToForce(distance,point){
        let change = distance/this.maxHeight;
        return (1/change - 1)*this.k*point.weight;
    }
    /**
     * Starting from the current node, search towards both sides
     * @param {*} p 
     * @returns return the points which has force with this points
     */
    forcePoints(p){
        let resultPoints = [];
        let index = this.nodes.indexOf(p);
        let maxIndex = this.nodes.length;
        for(let k = index+1;k<maxIndex;k+=1){
            if(Math.abs(p.x-this.nodes[k].x) < this.maxHeight){
                let dist = this.distance(p,this.nodes[k]);
                if(dist < this.maxHeight){
                    if(dist == 0){
                        let yx = this.nodes[k].y += this.maxHeight/100;
                        resultPoints.push({...this.nodes[k],distance:dist,y:yx});
                    }else{
                        resultPoints.push({...this.nodes[k],distance:dist});
                    }
                }
                continue;
            }
            break;
        }
        for(let k = index-1;k>=0;k-=1){
            if(Math.abs(p.x-this.nodes[k].x) < this.maxHeight){
                let dist = this.distance(p,this.nodes[k]);
                if(dist < this.maxHeight){
                    if(dist == 0){
                        let yx = this.nodes[k].y += this.maxHeight/100;
                        resultPoints.push({...this.nodes[k],distance:dist,y:yx});
                    }else{
                        resultPoints.push({...this.nodes[k],distance:dist});
                    }
                }
                continue;
            }
            break;
        }
        return resultPoints;
    }

    /**
     * 
     * @param {*} p 
     * @returns to complate points all force
     */
    getPointForce(p){
        const reativePoints = this.forcePoints(p);
        // console.log('reative',reativePoints)
        let pointForce = 0;
        for(let i=0;i<reativePoints.length;i++){
            pointForce += this.force(p,reativePoints[i]);
        }
        let upForce =  this.maxHeight/(p.y - this.maxHeight) + 1;
        let downForce = this.maxHeight / p.y - 1;
        return  pointForce + upForce + downForce;
    }

    /**
     * move point by distance
     * @param {*} p 
     * @param {*} distance 
     */
    movePointByDistance(p,distance){
        p.y += distance
    }

    
    /**
     * 这里有问题，不能根据力来计算距离，就要每个步长叠加走。
     * @param {*} force 
     * points should move distance with the force 
     */

    /**
     * convert distance y to y axis value;
     */
    convertToY(){
        this.nodes.map(item => {
            if(item.y > 400) item.y == 400;
            item.y = this.yValue / this.maxHeight * item.y;
        })
    }

    /**
     * 
     * @param {*} arr 
     * @returns return the variance of array.
     */
    variance(arr) {
        let mean = arr.reduce((acc, val) => acc + val, 0) / arr.length;
        let variance = arr.reduce((acc, val) => acc + (val - mean) ** 2, 0) / arr.length;
        return {
            mean,
            variance
        };
    }

    isEndState(forceList){
        const staticPoints = this.variance(forceList);
        return staticPoints.mean < this.endCondition.mean && staticPoints.variance < this.endCondition.variance;
    }

    movePoint(){
        const pointFirst = this.nodes[0];
        if(!this.startMove){
            this.startMove = true;
            pointFirst.moveStep = -this.defaultStep;  //the first point move down by default distances
            pointFirst.y += pointFirst.moveStep;
        }
        const pointsForceList = [];
        let runtimes = 0;
        do{
            runtimes++;
            pointsForceList.length = 0;
            for(let i = 0;i<this.nodes.length;i++){
                const point = this.nodes[i];
                let moveTimes = 0;
                let pointForce = this.getPointForce(point);
                pointsForceList.push(pointForce);
                if(pointForce == 0) continue;
                if(point.moveStep == 0 ){
                    point.moveStep = Math.abs(pointForce) / pointForce * this.defaultStep;
                }
                while(Math.abs(pointForce) > this.minForce && moveTimes <= 50) {
                    if(pointForce * point.moveStep > 0) {
                        point.y += point.moveStep;
                    }else{
                        point.moveStep = point.moveStep*(-1)/2
                        point.y += point.moveStep;
                    }
                    moveTimes++;
                    pointForce =  this.getPointForce(point);

                }
            }
            console.log(runtimes,this.variance(pointsForceList).mean,this.variance(pointsForceList).variance)
        } while(!this.isEndState(pointsForceList))

        this.convertToY();
        return this.nodes;
    }
}

export default GenerateCoor;